package momo.multitree.util;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;

import momo.multitree.structure.Edge;
import momo.multitree.structure.Graph;
import momo.multitree.structure.LatencyPair;
import momo.multitree.structure.Node;
import momo.multitree.structure.NodePair;
import momo.multitree.structure.Tree;

public class WeightCalculator {
	
	private Tree tree;
	private Graph graph;
	
	private double maxLat;
	private double maxCost;
	private double maxStab;
	
	private double latWeightage;
	private double costWeightage;
	private double stabWeightage;
	
	public final static double DEFAULT_LATWEIGHTAGE = 1.0/3.0;
	public final static double DEFAULT_COSTWEIGHTAGE = 1.0/3.0;
	public final static double DEFAULT_STABWEIGHTAGE = 1.0/3.0;
	
	public WeightCalculator(Tree tree, Graph graph, double latWeightage, double costWeightage, double stabWeightage) {
		this.tree = tree;
		this.graph = graph;
		this.maxLat = this.getMaxLat();
		this.maxCost = this.getMaxCost();
		this.maxStab = this.getMaxStab();
		this.latWeightage = latWeightage;
		this.costWeightage = costWeightage;
		this.stabWeightage = stabWeightage;
	}
	
	public WeightCalculator(Tree tree, Graph graph) {
		this(tree, graph, DEFAULT_LATWEIGHTAGE, DEFAULT_COSTWEIGHTAGE, DEFAULT_STABWEIGHTAGE);
	}
	
	public double calWeight() {
		
		double weight = 0;
		
		Node root = tree.getRoot();
		NodePair pairs = new NodePair();
		HashSet<Node> visited = new HashSet<Node>();
		Vector<Node> toVisit = new Vector<Node>();
		
		toVisit.add(root);
		while ( toVisit.size() > 0 ) {
			Node currNode = toVisit.remove(0);
			visited.add(currNode);
			
			Set<Edge> set = tree.getEdgesOfNode(currNode);
			Iterator<Edge> iter = set.iterator();
			
			while ( iter.hasNext() ) {
				Edge edge = iter.next();
				Node toNode = edge.nextNode(currNode);
				double currWeight = this.getWeight(edge, currNode);
				
				if ( !visited.contains(toNode) ) {
					//downstream link
					toVisit.add(toNode);
					//adds up all the downlinks
					weight += currWeight;
				}else {
					//upstream link
					double prevWeight = pairs.getPair(toNode, root);
					pairs.setPair(currNode, root, prevWeight + currWeight);
				}
			}
		}
		
		Set<Node> nodes = tree.getNodes();
		Iterator<Node> iter = nodes.iterator();
		while(iter.hasNext())
		{
			Node currNode = iter.next();
			weight += ( currNode.getWorkloadRate() *  pairs.getPair(currNode, root) );
		}
		return weight;
	}
	
	public double getWeight(Edge edge, Node startNode) {
		double lat = edge.getLatency(startNode);
		double cost = edge.getCost(startNode);
		double stab = startNode.getStability();
		return this.getWeight(lat, cost, stab);
	}
	
	public double getWeight(double lat, double cost, double stab) {
		double scaled_lat = lat/maxLat;
		double scaled_cost = cost/maxCost;
		double scaled_stab = stab/maxStab;
		double weight = (latWeightage * scaled_lat) + (costWeightage * scaled_cost) + (stabWeightage * scaled_stab) ;
		return weight;
	}
	
	public double getMaxLat() {
		double max = 0;
		Edge[] edgeSet = graph.getEdges().toArray(new Edge[0]);
		max = edgeSet[0].getLatency(edgeSet[0].getHeadNode());
		for ( Edge e: edgeSet ) {
			double new_max = Math.max(e.getLatency(e.getHeadNode()), e.getLatency(e.getTailNode()));
			max = Math.max(max, new_max);
		}
		return max;
	}
	
	public double getMaxCost() {
		double max = 0;
		Edge[] edgeSet = graph.getEdges().toArray(new Edge[0]);
		max = edgeSet[0].getCost(edgeSet[0].getHeadNode());
		for ( Edge e: edgeSet ) {
			double new_max = Math.max(e.getCost(e.getHeadNode()), e.getCost(e.getTailNode()));
			max = Math.max(max, new_max);
		}
		return max;
	}
	
	public double getMaxStab() {
		double max = 0;
		Node[] nodeSet = graph.getNodes().toArray(new Node[0]);
		max = nodeSet[0].getStability();
		for ( Node n : nodeSet )
			max = Math.max(max, n.getStability());
		return max;
	}
		
}//end of class WeightCalculator
